package com.gui.mainwindow;

import com.Utils;
import com.data.*;
import com.effects.BasicEffect;
import com.gooddies.events.Lookup;
import com.gooddies.swing.hList;
import com.gooddies.texteditors.DefaultTextField;
import com.gooddies.utils.FileUtils;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.gui.musiceditor.MusicFileEditorDialog;
import com.gui.settings.SettingsDialog;
import com.gui.universe.UniverseEditorDialog;
import com.gui.wheel.WheelEditorDialog;
import com.music.MusicEvent;
import com.music.MusicManager;
import com.music.MusicProvider;
import com.server.*;
import com.swingson.SwingsonGuiBuilder;
import com.swingson.exceptions.ErrorMessageException;

import javax.imageio.ImageIO;
import javax.mail.MessagingException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.*;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * @author sad
 */
public class MainWindow extends JFrame implements MusicProvider {

    private static MainWindow instance;
    private final hList<Universe> universeList = new hList<>();
    private final hList<Wheel> wheelsList = new hList<>();
    private final hList<MusicFile> musicList = new hList<>();
    private JComponent deleteWheelMenu;
    private JComponent deleteMusicMenu;
    private JLabel serverStatusLabel;
    private JLabel clientStatusLabel;
    private JButton serverControlButton;
    private Server server;
    private final JCheckBox continiousCB = new JCheckBox();
    private Timer plasmaTimer;
    private Timer wheelTimer;
    private BasicEffect effect;
    private BufferedImage lastEffectImage;
    private Settings settings;
    private BufferedImage worldMapImage;
    private final DefaultTextField red = new DefaultTextField();
    private final DefaultTextField green = new DefaultTextField();
    private final DefaultTextField blue = new DefaultTextField();
    private final DefaultTextField positionFrom = new DefaultTextField();
    private final DefaultTextField positionTo = new DefaultTextField();
    private final DefaultTextField universe = new DefaultTextField();
    private final DefaultTextField changeWheelDirectionInterval= new DefaultTextField();


    public MainWindow() throws HeadlessException {
        Utils.lhTo16Bit((byte) 0, (byte) 0);
        instance = this;

        red.setText("255");
        green.setText("255");
        blue.setText("255");
        positionFrom.setText("0");
        positionTo.setText("170");
        universe.setText("0");
        changeWheelDirectionInterval.setText("10");

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        initTitle();
        initLists();
        MusicManager.getInstance().setMusicProvider(this);
        MusicManager.getInstance().setProgressEvent(createMusicProgressEvent());
        loadData();
        SwingsonGuiBuilder.createGuiFromJsonInPackage(this);
        initServer();
        loadWorldMapImage();
        startWheelSpeedProcessorTimer();
        WheelManager.getInstace().setOnWheelSpeedUpdated(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                onWheelSpeedUpdated();
            }
        });
    }

    private void loadWorldMapImage() {
        try {
            BufferedImage newImage = Utils.createOffscreenImage();
            BufferedImage worldImage = ImageIO.read(new File("./pics/map2.bmp"));
            Graphics g = newImage.getGraphics();
            g.drawImage(worldImage, 0, 0, this);
            g.dispose();
            worldMapImage = newImage;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static MainWindow getInstance() {
        return instance;
    }

    private void stopMusic() {
        MusicManager.getInstance().stopMusicPlayedById();
        stopEffects();
    }

    private void onTestSetPixelClick() {
        int uIndex = Integer.valueOf(universe.getText());

        int pixelIndexFrom = Integer.valueOf(positionFrom.getText());
        int pixelIndexTo = Integer.valueOf(positionTo.getText());
        byte redValue = Integer.valueOf(red.getText()).byteValue();
        byte greenValue = Integer.valueOf(green.getText()).byteValue();
        byte blueValue = Integer.valueOf(blue.getText()).byteValue();

        Universe universe = UniverseManager.getInstance().getUniverse(uIndex);
        ArtNetProcessor artNetProcessor = new ArtNetProcessor();
        artNetProcessor.sendPixelUniverse(pixelIndexFrom, pixelIndexTo, universe, redValue, greenValue, blueValue);
    }

    public BasicEffect getEffect() {
        return effect;
    }

    public void setEffect(BasicEffect effect) {
        stopEffects();
        this.effect = effect;
    }

    private void initServer() {
        server = new Server();
    }

    private void initTitle() {
        setTitle("Dmx Light Controller. Stopped");
        Lookup.getDefault().addChangeEvent("serverstatus", new Lookup.LookupEventChangedValue<Boolean>() {

            @Override
            public void change(Boolean serverStatus) {
                String statusMessage = serverStatus == true ? "Started" : "Stopped";
                setTitle("Dmx Light Controller. " + statusMessage);
                serverStatusLabel.setText(statusMessage);
                serverControlButton.setText(serverStatus ? "Stop" : "Start");
                if (serverStatus == true) {
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                }
            }
        });
        Lookup.getDefault().addChangeEvent("clientstatus", new Lookup.LookupEventChangedValue<Boolean>() {

            @Override
            public void change(Boolean clientStatus) {
                String statusMessage = clientStatus == true ? "Connected" : "Not connected";
                clientStatusLabel.setText(statusMessage);
                if (clientStatus == false) {
                    stopMusic();
                    stopTimers();
                    stopWheelTimer();
                    stopEffects();

                    WheelManager.getInstace().stopWheels();

                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                    drawImageToLED(worldMapImage, false);
                }
            }
        });
    }

    private void initLists() {
        universeList.clearItems();
        wheelsList.clearItems();
        for (Universe universe : UniverseManager.getInstance().iterator()) {
            universeList.addItem(universe);
        }
    }

    private MusicEvent createMusicProgressEvent() {
        return new MusicEvent() {
            int i = -1;

            @Override
            public void progress(byte[] pcm) {
                if (effect == null) {
                    return;
                }
                i++;

                BufferedImage image = effect.getImage(pcm);
                lastEffectImage = image;
                if (image != null) {
                    drawImageToLED(image, false);
                    server.resendPictureToClient(image);
                }

            }
        };
    }

    private void onContiniousClicked() {
        Properties.isContinious = continiousCB.isSelected();
        saveData();
    }

    protected void onCreateWheel() {
        wheelsList.addItem(new Wheel());
    }

    protected void onCreateMusicFile() {
        musicList.addItem(new MusicFile());
    }

    protected void onWheelMenuPopup() {
        deleteWheelMenu.setEnabled(wheelsList.getSelectedItem() != null);
    }

    protected void onMusicMenuPopup() {
        deleteMusicMenu.setEnabled(musicList.getSelectedItem() != null);
    }

    protected void onDeleteWheel() {
        int index = wheelsList.getSelectedIndex();
        wheelsList.removeObject(index);
        cacheWheels();
        saveData();
    }

    protected void onDeleteMusicFile() {
        int index = musicList.getSelectedIndex();
        musicList.removeObject(index);
        saveData();
    }

    private void stopTimers() {
        if (plasmaTimer != null) {
            plasmaTimer.stop();
            plasmaTimer = null;
        }
        stopWheelTimer();
    }

    private void stopEffects() {
        effect = null;
        lastEffectImage = null;
    }

    private void onTestPlasmaButtonClick() {
        stopEffects();
        stopMusic();
        stopTimers();

        final Plasma plasma = new Plasma();
        setTitle("Dmx Light Controller. Plasma");

        plasmaTimer = new Timer(50, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                plasma.update();
                drawImageToLED(plasma.getImage(), false);
                server.resendPictureToClient(plasma.getImage());
            }
        });
        plasmaTimer.setRepeats(true);
        plasmaTimer.start();
    }

    protected void onWheelListMousePress(MouseEvent event) {
        if (wheelsList.getSelectedItem() == null) {
            return;
        }

        if (!event.isPopupTrigger() && event.getClickCount() == 2) {
            Wheel wheel = wheelsList.getSelectedItem();
            WheelEditorDialog dialog = new WheelEditorDialog(this, wheel);
            dialog.pack();
            dialog.setLocationRelativeTo(null);
            dialog.setVisible(true);
            wheelsList.repaint();
            cacheWheels();
            MusicManager.getInstance().restart();
            saveData();
        }
    }

    protected void onMusicListMousePress(MouseEvent event) {
        if (musicList.getSelectedItem() == null) {
            return;
        }

        if (!event.isPopupTrigger() && event.getClickCount() == 2) {
            MusicFile musicFile = musicList.getSelectedItem();
            MusicFileEditorDialog dialog = new MusicFileEditorDialog(this, musicFile);
            dialog.pack();
            dialog.setLocationRelativeTo(null);
            dialog.setVisible(true);
            musicList.repaint();
            saveData();
        }
    }

    protected void onUniverseListMousePress(MouseEvent event) {
        if (universeList.getSelectedItem() == null) {
            return;
        }

        if (!event.isPopupTrigger() && event.getClickCount() == 2) {
            Universe universe = universeList.getSelectedItem();
            UniverseEditorDialog editorDialog = new UniverseEditorDialog(this, universe);
            editorDialog.pack();
            editorDialog.setLocationRelativeTo(null);
            editorDialog.setVisible(true);
            saveData();
            MusicManager.getInstance().restart();
        }
    }

    protected void onServerStart() {
        stopTimers();
        if (server.isUp()) {
            server.stop();
        } else {
            server.start();
        }
    }

    private void drawImageToLED(BufferedImage image, boolean useWheel) {
        try {
            lastEffectImage = image;
            BufferedImage lastClientImage = server.getLastImageFromClient();//Utils.showImageDebug(image);
            BufferedImage resultImage;
            if (lastClientImage != null) {
                resultImage = combineLayers(lastClientImage);//Utils.showImageDebug(lastClientImage);
            } else {
                resultImage = image;
            }
            byte[] buffer = new byte[39 * 78 * 3];//Utils.showImageDebug(resultImage);
            Utils.copyImageToBuffer(resultImage, buffer);

            server.getArtNetProcessor().sendPicture(buffer);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new ErrorMessageException(ex.getMessage());
        }
    }

    private BufferedImage convertImageToBGR(BufferedImage image) {
        BufferedImage ti = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
        Graphics g = ti.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return ti;
    }

    protected void onTestButtonClick() {
        stopMusic();
        stopTimers();
        try {
            BufferedImage image = ImageIO.read(MainWindow.class.getResourceAsStream("/resources/testPic2.png"));
            image = convertImageToBGR(image);
            drawImageToLED(image, true);
            server.resendPictureToClient(image);
        } catch (IOException ex) {
            ex.printStackTrace();
            throw new ErrorMessageException(ex.getMessage());
        }
    }

    protected void onTestClearButtonClick() {
        stopTimers();
        byte[] buffer = new byte[39 * 78 * 3];
        WheelManager.getInstace().stopWheels();
        stopMusic();
        server.getArtNetProcessor().sendPicture(buffer);
        server.resendPictureToClient(new BufferedImage(78, 39, BufferedImage.TYPE_3BYTE_BGR));
    }

    private void loadData() {
        File file = new File("./saved.txt");
        if (!file.exists()) {
            return;
        }

        String value = FileUtils.readToString(file, "UTF-8");
        SavedFile saved = new Gson().fromJson(value, SavedFile.class);
        for (int i = 0; i < UniverseManager.getInstance().universesCount(); i++) {
            Universe u = UniverseManager.getInstance().getUniverse(i);
            Universe savedUniverse = saved.getUniverses().get(i);
            u.setIndex(savedUniverse.getIndex());
            u.setIp(savedUniverse.getIp());
            u.setPort(savedUniverse.getPort());
            u.setPosition(savedUniverse.getPosition());
            u.setSubNet(savedUniverse.getSubNet());
        }

        musicList.clearItems();
        if (saved.getMusicFiles() != null) {
            musicList.addItems(saved.getMusicFiles());
        }

        for (WheelWrapper wrapper : saved.getWheelWrappers()) {
            Wheel wheel = wrapper.getWheel();
            wheel.setUniverse(UniverseManager.getInstance().getUniverse(wrapper.getUniverseId()));
            wheel.setCurrentSpeed(0);
            wheelsList.addItem(wheel);
        }

        continiousCB.setSelected(saved.isIsContinious());
        Properties.isContinious = saved.isIsContinious();
        cacheWheels();
        if (saved.getSettings() == null) {
            settings = new Settings();
            settings.setCameraFrameRate(25);
            settings.setCameraPassword("");
            settings.setCameraUrl("");
            settings.setCameraUser("");
            settings.setFaceBookEmail("");
            settings.setFaceBookPassword("");
            settings.setFacebookAppId("");
            settings.setFacebookAppSecret("");
            settings.setMailAddress("");
            settings.setMailServer("");
            settings.setMailPassword("");
        } else {
            settings = saved.getSettings();
        }

        MusicManager.getInstance().restart();
    }

    public Settings getSettings() {
        return settings;
    }

    private void cacheWheels() {
        WheelManager.getInstace().clear();
        WheelManager.getInstace().processWheels(wheelsList.iterable());
        UniverseManager.getInstance().recalculateMasks();
    }

    private void saveData() {
        SavedFile savedFile = new SavedFile();
        List<Universe> universes = new ArrayList<>();
        for (Universe u : UniverseManager.getInstance().iterator()) {
            universes.add(u);
        }

        List<WheelWrapper> wheels = new ArrayList<>();
        for (Wheel w : wheelsList.iterable()) {
            int universeIndex = universes.indexOf(w.getUniverse());
            WheelWrapper wrapper = new WheelWrapper(w, universeIndex);
            wheels.add(wrapper);
        }

        List<MusicFile> musicFiles = new ArrayList<>();
        for (MusicFile mf : musicList.iterable()) {
            musicFiles.add(mf);
        }
        savedFile.setMusicFiles(musicFiles);
        savedFile.setUniverses(universes);
        savedFile.setWheelWrappers(wheels);
        savedFile.setSettings(settings);
        savedFile.setIsContinious(continiousCB.isSelected());
        String result = new GsonBuilder().setPrettyPrinting().setExclusionStrategies(new ExclusionStrategy() {

            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return f.hasModifier(Modifier.TRANSIENT);
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return false;
            }
        }).create().toJson(savedFile);
        try (PrintStream stream = new PrintStream(new File("./saved.txt"), "UTF-8")) {
            stream.print(result);
        } catch (UnsupportedEncodingException | FileNotFoundException ex) {
            ex.printStackTrace();
            throw new ErrorMessageException(ex.getMessage());
        }
    }

    @Override
    public MusicFile[] getMusicFiles() {
        MusicFile[] files = new MusicFile[musicList.getItemCount()];
        for (int i = 0; i < musicList.getItemCount(); i++) {
            files[i] = musicList.getItem(i);
        }

        return files;
    }

    private final BufferedImage combinedImage = Utils.createOffscreenImage();
    private final int[] tempColorBuffer = new int[3];

    public BufferedImage combineLayers(BufferedImage image) {
        Graphics g = combinedImage.getGraphics();//Utils.showImageDebug(image);
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, combinedImage.getWidth(), combinedImage.getHeight());
        if (lastEffectImage != null) {
            g.drawImage(lastEffectImage, 0, 0, this);
        }

        g.dispose();//Utils.showImageDebug(combinedImage);
        if (image != null) {
            WritableRaster srcRaster = image.getRaster();
            WritableRaster destRaster = combinedImage.getRaster();
            for (int j = 0; j < image.getHeight(); j++) {
                for (int i = 0; i < image.getWidth(); i++) {
                    srcRaster.getPixel(i, j, tempColorBuffer);
                    if (!(tempColorBuffer[0] == 0x0 && tempColorBuffer[1] == 0x0 && tempColorBuffer[2] == 0x0)) {
                        destRaster.setPixel(i, j, tempColorBuffer);
                    }
                }
            }
        }

        g = combinedImage.getGraphics();
        g.setColor(Color.BLUE.brighter());
        for (Wheel w : WheelManager.getInstace().getWheels()) {
            if (w.getCurrentSpeed() != 0) {
                int angle = w.getCurrentAngle();
                drawRaysFromWheel(w, g, angle);
                drawRaysFromWheel(w, g, angle + 90);
                drawRaysFromWheel(w, g, angle + 180);
                drawRaysFromWheel(w, g, angle + 270);
                int speed = WheelManager.getInstace().getActualSpeedValueForExpectedSpeed(w.getCurrentSpeed());
                float addToAngleSpeed = speed / 4;
                angle += (addToAngleSpeed);
                w.setCurrentAngle(angle);
            }
        }

        g.dispose();
        return combinedImage;
    }

    private void drawRaysFromWheel(Wheel w, Graphics g, int angle) {
        int rayLength = 8;
        int x = w.getScreenX();
        double ta = Math.toRadians(angle);
        int endX = (int) (Math.cos(ta) * rayLength + x);
        int endY = (int) (Math.sin(ta) * rayLength + w.getScreenY());
        g.drawLine(endX, endY, x, w.getScreenY());
    }

    public void clear() {
        Utils.fillImageWithBlack(combinedImage);
        lastEffectImage = null;
    }

    private void calculateWheelsSpeeds() {
        int interval=0;
        try {
            interval = Integer.valueOf(changeWheelDirectionInterval.getText());
        } catch(Exception e){

        }
        WheelManager.getInstace().processWheelsSpeed(interval);
    }

    public void onWheelSpeedUpdated() {
        if (wheelTimer != null || effect != null) {
            return;//we should do nothing. Wheels will be updated from these timers
        }
        //if no wheel timers, or no running visual effect, we manually update wheels speed on dmx device
        System.out.println("Speed changed after client disconnected");
        drawImageToLED(lastEffectImage, true);
    }

    private Timer wheelSpeedProcessorTimer;
    private void startWheelSpeedProcessorTimer() {
        wheelSpeedProcessorTimer = new Timer(20, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                calculateWheelsSpeeds();
            }
        });
        wheelSpeedProcessorTimer.setRepeats(true);
        wheelSpeedProcessorTimer.setCoalesce(true);
        wheelSpeedProcessorTimer.start();
    }

    public void stopWheelTimer() {
        if (wheelTimer != null) {
            wheelTimer.stop();
            wheelTimer = null;
        }
    }

    public void startWheelTimer() {
        if (effect != null) {//we should not run wheel update timer if we have effect(it has own timer)
            return;
        }
        stopWheelTimer();
        wheelTimer = new Timer(50, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                drawImageToLED(lastEffectImage, true);
            }
        });

        wheelTimer.setCoalesce(true);
        wheelTimer.setRepeats(true);
        wheelTimer.start();
    }

    private void onSettingsClicked() {
        SettingsDialog dialog = new SettingsDialog(this, settings);
        dialog.pack();

        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);
        saveData();
    }

    private void onTestMail() {
        Settings settings = MainWindow.getInstance().getSettings();
        MailSender sender = new MailSender(settings.getMailServer(), settings.getMailAddress(), settings.getMailPassword());

        try {
            sender.sendMesssage("TestEmail", Client.getContent("http://testUrl.com"), "klwartofmotion@gmail.com");
        } catch (MessagingException ex) {
            ex.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}



